package cn.ibizlab.core.authentication.provider;

import cn.ibizlab.core.ad.domain.SysPerson;
import cn.ibizlab.core.ad.service.SysPersonService;
import cn.ibizlab.core.authentication.constants.AuthenticationConstant;
import cn.ibizlab.core.authentication.domain.AuthProvider;
import cn.ibizlab.core.authentication.service.impl.AuthProviderServiceImpl;
import cn.ibizlab.core.authentication.service.impl.AuthUserServiceImpl;
import cn.ibizlab.core.authentication.service.impl.LdapAuthenticationService;
import cn.ibizlab.util.enums.Entities;
import cn.ibizlab.util.errors.BadRequestAlertException;
import cn.ibizlab.util.security.AuthenticationUser;
import cn.ibizlab.util.service.AuthenticationUserService;
import cn.ibizlab.util.web.Sm3Util;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.stereotype.Component;
import org.springframework.util.ObjectUtils;
import static cn.ibizlab.core.authentication.constants.AuthenticationConstant.*;

@Slf4j
@Component
public class LdapAuthenticationProvider implements AuthenticationProvider {

    @Value("${ibiz.auth.passwordStorageMode:none}")
    private String passwordStorageMode;
    @Autowired
    AuthenticationUserService authenticationUserService;
    @Autowired
    AuthUserServiceImpl authUserService;
    @Autowired
    AuthProviderServiceImpl authProviderService;
    @Autowired
    SysPersonService sysPersonService;
    @Autowired
    AuthenticationServiceManager authenticationServiceManager;

    /**
     * 调用ldap服务进行验证
     *
     * @param authentication
     * @return
     * @throws AuthenticationException
     */
    @Override
    public Authentication authenticate(Authentication authentication) {

        AuthProvider provider = authProviderService.first(AuthenticationConstant.PROVIDER_LDAP);
        if(provider == null)
            throw new BadRequestAlertException("未配置ldap认证服务", Entities.AUTH_PROVIDER.toString(), "First Ldap");

        LdapAuthenticationService ldapService = (LdapAuthenticationService) authenticationServiceManager.getAuthenticationService(provider);
        String username = !ObjectUtils.isEmpty(authentication.getPrincipal()) ? authentication.getPrincipal().toString() : null;
        String password = !ObjectUtils.isEmpty(authentication.getCredentials()) ? authentication.getCredentials().toString() : null;
        String authenticationType = String.format("%1$s-%2$s", ldapService.getProvider().getAuthenticationType(), ldapService.getServiceType());

        log.debug(String.format("正在使用[%1$s]认证方式对[%2$s]用户登录进行验证", authenticationType, username));
        Authentication authenticate = ldapService.authenticate(authentication);
        if (authenticate != null) {
            //注销登录清除用户缓存
            authenticationUserService.resetByUsername(username);
            //获取认证用户信息
            SysPerson person = ldapService.lookup(username);
            if (person == null) {
                log.error(String.format("获取[%1$s]用户[%2$s]信息失败", authenticationType, username));
                return null;
            }
            person.setUserPassword(password);
            //同步用户到数据库
            authUserService.syncUser2DataBase(person, authenticationType);
            //获取当前用户信息(角色及权限)
            AuthenticationUser user = authenticationUserService.loadUserByUsername(username);
            //构造认证信息
            UsernamePasswordAuthenticationToken result = new UsernamePasswordAuthenticationToken(user, authentication.getCredentials(), user.getAuthorities());
            result.setDetails(user);
            return result;

        } else if(PROVIDER_LDAP_DEFAULT_PROVIDER_ID.equals(provider.getId())){
            //启用默认ldap配置时，将数据库用户同步到ldap。在auth_provider中配置的ldap仅作为认证使用，不进行用户同步
            SysPerson person = loadUserByUsername(username);
            if (person != null) {
                String encodePassword = password;
                if (AuthenticationConstant.PASSWORD_STORAGE_MODE_SM3.equals(passwordStorageMode) && password != null && password.length() != 64)
                    encodePassword = Sm3Util.encrypt(password);

                //校验密码是否正确
                if (encodePassword != null && encodePassword.equals(person.getUserPassword())) {
                    //将加密前原始密码存储至ldap
                    person.setUserPassword(password);
                    //将数据库用户同步到ldap
                    ldapService.syncDatabaseUser2Ldap(person);
                }
            }
        }
        return null;
    }

    /**
     * 判断provider中是否有type = ldap的配置 , 且ldap配置优先级最高
     *
     * @param authentication
     * @return
     */
    @Override
    public boolean supports(Class<?> authentication) {
        return authProviderService.first(AuthenticationConstant.PROVIDER_LDAP) != null;
    }

    /**
     * 通过用户名查询用户信息
     *
     * @param username
     * @return
     */
    protected SysPerson loadUserByUsername(String username) {
        LambdaQueryWrapper<SysPerson> conditions = new LambdaQueryWrapper<>();
        String[] usernameArrays = username.split("[|]");
        String loginName = "";
        String domains = "";

        //查询数据库用户是否存在
        if (usernameArrays.length > 0)
            loginName = usernameArrays[0].trim();
        if (usernameArrays.length > 1)
            domains = usernameArrays[1].trim();

        if (!ObjectUtils.isEmpty(loginName))
            conditions.eq(SysPerson::getUid, loginName);
        if (!ObjectUtils.isEmpty(domains))
            conditions.eq(SysPerson::getDc, domains);

        SysPerson person = sysPersonService.getOne(conditions);
        return person;
    }
}
